知られざる WAVE ファイルの中身
音楽ファイルのもっとも基本である WAVE ファイル。
音楽データを扱うプログラムを書いたことがある人なら、
一度はその中身について勉強したことがあるでしょう。
しかし、実は WAVE ファイルの中にはあまり世に知られていない、
マニアックなデータが埋め込まれていることもあります。
今回は、その辺を深く掘り下げてみましょう。
Introduction
今回は WAVE ファイルの中身について触れるわけですが、
実際に入っているデータ部分(PCM)ではなく、
そのおまけでくっついているデータについての解説です。
WAVE ファイルは RIFF - Resource Interchange File Format というフォーマットで保存されています。 この RIFF という単語も、よく聞く単語です。 こいつの作りは超簡単で、効率よくデータの追加・削除が行える設計になっています。
次の章では、まずは簡単に RIFF の構造を学んでみましょう。
WAVE ファイルは RIFF - Resource Interchange File Format というフォーマットで保存されています。 この RIFF という単語も、よく聞く単語です。 こいつの作りは超簡単で、効率よくデータの追加・削除が行える設計になっています。
次の章では、まずは簡単に RIFF の構造を学んでみましょう。
RIFF 概説
RIFF での決まりは次の事柄です。
- 『チャンク』と呼ばれる、データの集まりで構成されている
- チャンクには 32 ビットの名前が付いている
- チャンクの中にチャンクを持つことができる
- チャンクの境界は 16 ビット
- そのファイルに必要のないチャンクを入れてもよい
上から見ていきましょう。まずチャンクです。
というより、RIFF を構成するのがこのチャンクの一言に尽きます。
このチャンクというのは、中に実際のデータが入っている箱のようなものです。
つまり、WAVE ファイルの中にはいくつかの箱が入っており、
その箱を開けると目的のデータが入っているということです。
イメージでいうとこんな感じ。

次の、チャンクの名前、というものです。
ファイルを開けて箱があるものの、どれが何の箱なのか判らないと、データを使いようがありません。
そのため、チャンクには必ず名前がついています。
上の例だと、RIFF, WAVE, fmt , data がそうです。
このように、名前は 32 ビットの、ASCII コードから成り立っています。
これを、FourCC(おそらく Four Character Codes)と呼んでいます。
fmt にも、正確には t の後ろにスペースが入っています。
お次。チャンクの中にチャンクを持つことができる、というもの。
これは、箱を開けたらデータではなく、また箱が入っているという感じです。
もっというと、RIFF 自体がチャンクなので、その中に fmt やら data やらのチャンクが入っている、
ということになります。
チャンクの境界は 16 ビットです。
1 バイト分余ってしまったら、0x00 を入れて補完しなければいけません。
さて最後の、そのファイルには必要のないチャンクを入れてもよい、というところですが、
WAVE ファイルには必須チャンクとして fmt と data があります。
この 2 つは必ず入っていなければなりません。
ですが、その他に余計なチャンクを入れてもかまわない、ということです。
逆に、読み込む側は、そういう意味不明なチャンクが出てきたら、
ちゃんと読み飛ばすようにしなければなりません。
RIFF の詳しいフォーマット
RIFF の大まかな規約は以上ですが、
それでは煮え切らんという方のために、詳しいフォーマットを書いておきます。

とある WAVE ファイルの頭の部分です。まず、『RIFF』の文字があります。
これは、『このファイルは RIFF だよ〜』との宣言です。
次の 4 バイトはそのチャンクサイズです。 チャンクには、そのチャンクがどれだけのサイズを持っているかを示すサイズが必須となります。 あとは、基本的にチャンク名 + チャンクサイズ + 実データの繰り返しで保存されます。
さて、その次の 8 バイト目からは WAVE チャンクです。 『これは WAVE ファイルだよ〜』と言っているのです。 8 バイト目から始まるチャンクは特別で、サイズ定義を持ちません。
では次。『fmt 』です。これは、PCM のフォーマットが記録されています。 mmsystem.h にある『WAVEFORMAT』あるいは『WAVEFORMATEX』の形で入っています。 どちらで入っているかは、チャンクサイズを調べれば判ります。
次の 4 バイトは前述の通り、チャンクサイズ。 言い忘れましたが、 チャンクサイズはチャンク名 4 バイトとサイズ定義 4 バイト分の計 8 バイトは勘定に入っていません。 あくまで実データ部分のサイズということになります。 というわけで、その次の 14h バイト目から 10h はデータ部分となります。
次に『data』チャンクが出てきました。 ここに PCM データが格納されています。サイズもでかく、1a00918h もあります。
と、こんな感じです。非常に簡単でいいですね。
次の 4 バイトはそのチャンクサイズです。 チャンクには、そのチャンクがどれだけのサイズを持っているかを示すサイズが必須となります。 あとは、基本的にチャンク名 + チャンクサイズ + 実データの繰り返しで保存されます。
さて、その次の 8 バイト目からは WAVE チャンクです。 『これは WAVE ファイルだよ〜』と言っているのです。 8 バイト目から始まるチャンクは特別で、サイズ定義を持ちません。
では次。『fmt 』です。これは、PCM のフォーマットが記録されています。 mmsystem.h にある『WAVEFORMAT』あるいは『WAVEFORMATEX』の形で入っています。 どちらで入っているかは、チャンクサイズを調べれば判ります。
次の 4 バイトは前述の通り、チャンクサイズ。 言い忘れましたが、 チャンクサイズはチャンク名 4 バイトとサイズ定義 4 バイト分の計 8 バイトは勘定に入っていません。 あくまで実データ部分のサイズということになります。 というわけで、その次の 14h バイト目から 10h はデータ部分となります。
次に『data』チャンクが出てきました。 ここに PCM データが格納されています。サイズもでかく、1a00918h もあります。
と、こんな感じです。非常に簡単でいいですね。
色々なチャンク
さて、ここいらで本題に入りましょう。
WAVE ファイルの様々なデータというのは言うまでもなく、このチャンクの中に入っています。
正確には非公式なチャンクですが、一般的と思われるものをいくつかご紹介いたしましょう。
| LIST | これは WAVE ファイルに限ったことでなく、他の RIFF ファイルでも共通のことです。 LIST は上で触れた、チャンクの中にチャンクを持つための特別なチャンクです。 |
| cue | キューポイント、まぁなんかのための位置情報とでも言っておきましょうか。 サンプリング位置を指定して文字列を埋め込んだりできます。 肝心の中身ですが、私もよく判っていません。 ただ、キュー ID とサンプリング位置らしき値だけは読み取れるので、 それ以外は無視しています。 私はこのチャンクを使って WAVE ファイルを無限ループさせています。 |
| adtl | ファイルの詳しい内容を埋め込めるようです。これもリストチャンクです。 |
| labl | 先ほど定義したキューポイントにラベル文字を埋め込めます。 ループの始点と終点を記述するのに便利です。 |
まとめ
RIFF は基本なんでも埋め込めるので、
自作プログラムで扱うときは、あったほうがいい情報を埋め込みまくってもいいかもしれませんね。